Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 6, 2026

Add .slnx fallback for TestHost content root discovery

Description

This pull request adds .slnx fallback support to the test-only UseSolutionRelativeContentRoot method in the Microsoft.AspNetCore.TestHost. When searching for solution files with the *.sln pattern, the method now falls back to searching for .slnx files if no .sln file is found, preventing the InvalidOperationException that would previously occur before test developers can easily call UseSolutionRelativeContentRoot themselves.

This has already been fixed in .NET 10 by #61305, but that included public API changes, so this is not a direct backport. Unlike, the .NET 10 change that treats .slnx and .sln equivalently by default, this change will only fall back to looking for an *.slnx file if the test server would otherwise throw an InvalidOperationException due to not being able to find an ".sln" file.

Fixes #61304 in .NET 9.

Customer Impact

#61304 has gotten a lot of attention, because it is a pain point for migrating solution from .sln to .slnx.

System.InvalidOperationException : Solution root could not be located using application root C:\Project\Path\bin\Debug\net9.0\

Stack Trace:
 at Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(IWebHostBuilder builder, String solutionRelativePath, String applicationBasePath, String solutionName)
 at Microsoft.AspNetCore.TestHost.WebHostBuilderExtensions.UseSolutionRelativeContentRoot(IWebHostBuilder builder, String solutionRelativePath, String solutionName)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.SetContentRoot(IWebHostBuilder builder)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.<ConfigureHostBuilder>b__22_0(IWebHostBuilder webHostBuilder)
 at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(IHostBuilder builder, Func`3 createWebHostBuilder, Action`1 configure, Action`1 configureWebHostBuilder)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
 at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient()

Test projects that worked fine with an .sln-based solution throw after migrating to *.slnx before developers even get access to the IWebHostBuilder, so they don't get the opportunity to easily manually reconfigure the content root by calling UseSolutionRelativeContentRoot with "*.slnx". Instead, the current general workaround is fragile and unintuitive:

public static class MvcTestingAppManifestHelper
{
    private const string ManifestFileName = "MvcTestingAppManifest.json";
    private static readonly JsonSerializerOptions JsonSerializerOptions = new() { WriteIndented = true };

    public static void AddAssemblyToManifest(Assembly assembly)
    {
        if (!File.Exists(ManifestFileName))
        {
            return;
        }

        // The manifest file is a dictionary of Assembly.FullName to ContentRoot.
        var manifest = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(ManifestFileName))!;

        // Internally, the tilde is used to translate the content root to the AppContext.BaseDirectory.
        if (manifest.TryAdd(assembly.FullName!, "~"))
        {
            File.WriteAllText(ManifestFileName, JsonSerializer.Serialize(manifest, JsonSerializerOptions));
        }
    }
}

#61304 (comment)

Regression?

  • Yes
  • No

Not technically, but you could consider it a regression in behavior when migrating from .sln to .slnx solutions.

Risk

  • High
  • Medium
  • Low

This change only has an impact if starting the test server would otherwise throw an InvalidOperationException early during initialization due to not being able to find an ".sln" file.

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

  • Add non-breaking .slnx fallback for content root lookup.

Description

Problem: UseSolutionRelativeContentRoot threw when only a .slnx existed, and .slnx could shadow .sln in parents.
Changes:

  • Keep .sln precedence; if none found, retry search for .slnx without changing API.
  • Added focused tests validating .sln preference and .slnx fallback discovery.

Example:

new WebHostBuilder()
    .UseSolutionRelativeContentRoot("src/MyApp", appBasePath, "*.sln"); // now falls back to slnx if no sln
Original prompt

Create a variant of #61305 that contains no breaking API changes. It should also avoid the behavioral breaking change when you could technically have a .slnx file that then "shadows" a .sln file in a parent folder. The only behavioral change is that it should not throw the InvalidOperationException from https://github.com/dotnet/aspnetcore/blob/release/9.0/src/Hosting/TestHost/src/WebHostBuilderExtensions.cs#L166 if there was an "*.slnx" file.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@dotnet-policy-service dotnet-policy-service bot added this to the 9.0.x milestone Jan 6, 2026
@dotnet-policy-service
Copy link
Contributor

Hi @@copilot. If this is not a tell-mode PR, please make sure to follow the instructions laid out in the servicing process document.
Otherwise, please add tell-mode label.

Copilot AI changed the title [WIP] Update WebHostBuilderExtensions to avoid exception with .slnx file Add .slnx fallback for TestHost content root discovery Jan 6, 2026
Copilot AI requested a review from halter73 January 6, 2026 23:40
@halter73 halter73 marked this pull request as ready for review January 8, 2026 16:49
Copilot AI review requested due to automatic review settings January 8, 2026 16:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds non-breaking .slnx fallback support to the UseSolutionRelativeContentRoot method in TestHost. When searching for solution files with the "*.sln" pattern, the method now falls back to searching for .slnx files if no .sln file is found, preventing the InvalidOperationException that would previously occur.

Key changes:

  • Added .slnx fallback logic that only activates when no .sln file is found and the search pattern is "*.sln"
  • Maintained .sln precedence to avoid breaking changes where .slnx files could shadow .sln files in parent directories
  • Added comprehensive tests validating both the fallback behavior and .sln precedence

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Hosting/TestHost/src/WebHostBuilderExtensions.cs Added conditional .slnx fallback logic after .sln search fails, maintaining backward compatibility
src/Hosting/TestHost/test/WebHostBuilderExtensionsTests.cs New test file with two tests: one verifying .slnx fallback and one verifying .sln takes precedence over .slnx

@halter73
Copy link
Member

halter73 commented Jan 9, 2026

/backport to release/8.0

@github-actions
Copy link
Contributor

github-actions bot commented Jan 9, 2026

Started backporting to release/8.0 (link to workflow run)

@halter73 halter73 changed the title Add .slnx fallback for TestHost content root discovery [release/9.0] Add .slnx fallback for TestHost content root discovery Jan 10, 2026
@halter73 halter73 added the Servicing-consider Shiproom approval is required for the issue label Jan 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-consider Shiproom approval is required for the issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants